Go 垃圾回收:简单理解版

因为总也记不住, 必须从笔记当中挪出来放到博客中. 为了面子, 必须记住.

Go 的垃圾回收(GC)可以先简单理解为两个步骤:

标记存活对象 → 清除垃圾对象

这种方式叫作 标记—清除(Mark-Sweep)

第二阶段没什么知识点,就是把白色对象从堆中删除。

三色标记法

三色标记法描述的是在第一阶段,也就是标记存活对象时使用的方法。

三色标记过程

  1. 从 root 开始,找到所有可达对象,并标记为灰色。root 指:
    1. 全局变量;
    2. goroutine 栈中的变量;
  2. 扫描灰色对象,将它们标记为黑色,并将它们引用的白色对象标记为灰色;
  3. 重复步骤 2,直到没有灰色对象为止。

一次完整的标记过程

  1. 短暂 STW;
  2. 标记和业务代码并发运行;
  3. 短暂 STW。

STW

什么是 STW?

STW 是 Stop The World 的缩写,意思是暂停所有业务 goroutine 的运行,让 GC 独占 CPU 资源。

STW 都做了什么?

  1. 第一次 STW
    1. 暂停业务 goroutine;
    2. 统一开启写屏障;
    3. 恢复业务 goroutine。
  2. 第二次 STW
    1. 暂停业务 goroutine;
    2. 关闭写屏障;
    3. 最后清理一次写屏障引入的灰色节点;
    4. 恢复业务 goroutine。

为什么需要 STW?

保证所有业务 goroutine 都在同一个 GC 状态下运行,避免漏标存活对象。

为什么要使用三色标记?

三色标记法存在的意义是,能够让 GC 和业务代码同时运行。

  1. 如果一次扫描整个堆,并且扫描期间完全停止业务,那么堆越大,业务暂停的时间就可能越长。
  2. 三色标记可以满足让 GC 和业务代码并发运行的需求。
    1. GC 扫一半,让渡 CPU 资源给业务 goroutine,回来的时候,继续扫描剩下的灰色对象,黑色就不扫了;
    2. 但是业务 goroutine 可能会修改对象之间的引用关系,譬如:
      1. A --> B,然后 goroutine 切换;
      2. A --> C,这时候,B 是垃圾,C 不是了。这个时候,引入写屏障解决。

写屏障

写屏障可以简单理解为:业务代码修改指针引用时,由编译器插入的一小段辅助逻辑,用来通知 GC 对象引用关系发生了变化,避免并发标记过程中漏标存活对象。

常见的写屏障有两种:

插入写屏障

删除写屏障

删除写屏障的目的是什么?解决了什么场景的问题?

  1. B 是白色;
  2. 有 goroutine 引入了 B;
  3. B 真的变成白色了,这是不行的。

删除写屏障带来的问题是,垃圾回收不干净。譬如上面的 B 也许没有被引用,真的应该被回收。不过也没关系,因为下一轮 GC 会把它回收掉。

<完>

使用 Discussions 讨论 Github 上编辑 分享到 Twitter